﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
[CustomEditor(typeof(VerticesSeletor))]
public class VerticesSeletorEditor : Editor
{
    public override void OnInspectorGUI()
    {
        var vs = (VerticesSeletor)target;
        base.OnInspectorGUI();
        if (Application.isPlaying == false) return;
        if (GUILayout.Button("RemoveSkin"))
        {
            vs.wrapper.smr.RemoveSkin();
        }
        if (GUILayout.Button("BakeSkinMesh"))
        {
            vs.wrapper.BakeSkinMesh();
        }
    }
}
#endif
public class VerticesSeletor : MonoBehaviour
{
    private Mesh mesh;
    public SkinnedMeshRenderer smr;

    int vertexCount = 0;
    Vector3[] vertices;
    //Vector3[] normals;

    List<GameObject> points;
    public string pointShaderName = "Unlit/Color";
    public float pointSize = 0.03f;
    public float selectSize = 5;
    public GameObject prefab
    {
        get
        {
            if (_prefab == null) CreatePrefab();
            return _prefab;
        }
    }
    GameObject _prefab;
    [HideInInspector]
    public GameObject pointsContainer;
    public Action<int> selectChange;
    void CreatePrefab()
    {
        var cube = GameObject.CreatePrimitive(PrimitiveType.Cube);
        cube.name = "____pointPrefab";
        cube.transform.localScale = new Vector3(pointSize, pointSize, pointSize);
        var colider = cube.GetComponent<Collider>();
        Destroy(colider);
        var sphereColider = cube.AddComponent<SphereCollider>();
        sphereColider.radius *= selectSize;
        cube.layer = LayerMask.NameToLayer("Points");
        cube.GetComponent<MeshRenderer>().sharedMaterial = unSelect;
        _prefab = cube;
    }
    void Start()
    {
        Reset();

        selectIndex = -1;
        select = new Material(Shader.Find(pointShaderName));
        select.color = Color.red;
        unSelect = new Material(Shader.Find(pointShaderName));
        unSelect.color = Color.grey;

        points = new List<GameObject>();
        pointsContainer = new GameObject("points");
        pointsContainer.SetParent(transform.parent);

        //animation example
        for (int b = 0; b < mesh.vertexCount; b++)
        {
            var cube = Instantiate(prefab, pointsContainer.transform);
            var colider = cube.GetComponent<Collider>();
            Destroy(colider);
            cube.name = b.ToString();
            points.Add(cube);
        }
        prefab.SetActive(false);
    }
    float pickDistance = 9999;
    Material select;
    Material unSelect;
    public int selectIndex;
    [HideInInspector]
    public Transform preSelectPoint;

    bool ArrayEquals(RaycastHit[] a, RaycastHit[] b)
    {
        if (a == null || b == null) return false;
        if (a.Length != b.Length) return false;
        List<RaycastHit> A = new List<RaycastHit>(a);
        List<RaycastHit> B = new List<RaycastHit>(b);
        foreach (var i in A)
        {
            if (!B.Contains(i)) return false;
        }
        return true;
    }
    public RaycastHit[] preHits;
    public int hitSelectIndex;
    void CheckRay()
    {
        if (!Input.GetMouseButtonDown(0)) return;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        var mask = 1 << LayerMask.NameToLayer("Points");
        var hits = Physics.RaycastAll(ray, pickDistance, mask, QueryTriggerInteraction.Collide);
        if (hits == null || hits.Length == 0)
        {
            selectIndex = -1;
            preHits = hits;
            return;
        }
        if (ArrayEquals(preHits, hits))
        {
            hitSelectIndex++;
            if (hitSelectIndex >= hits.Length) hitSelectIndex = 0;
        }
        else { hitSelectIndex = 0; }
        preHits = hits;
        var hit = hits[hitSelectIndex];


        if (preSelectPoint != null) preSelectPoint.GetComponent<Renderer>().material = unSelect;
        hit.transform.GetComponent<Renderer>().material = select;
        preSelectPoint = hit.transform;
        selectIndex = int.Parse(hit.transform.name);

        ViewInfo();

        selectChange(selectIndex);
    }

    private void SetPosition()
    {
        if (selectIndex > -1)
        {
            SetPos(selectIndex, worldPos);
        }
    }

    void ViewInfo()
    {
        worldPos = GetPos(selectIndex);
    }

    public MeshWrapper wrapper;
    private void Reset()
    {
        wrapper = transform;
        mesh = wrapper.mesh;
        smr = wrapper.smr;
        vertexCount = mesh.vertexCount;
        vertices = new Vector3[vertexCount];
        boneMatrices = new Matrix4x4[smr.bones.Length];
    }
    Matrix4x4[] boneMatrices;
    void UpdateMatrix()
    {
        for (int i = 0; i < boneMatrices.Length; i++)
        {
            boneMatrices[i] = smr.bones[i].localToWorldMatrix * mesh.bindposes[i];
        }
    }
    public Vector3 GetPos(int b)
    {
        BoneWeight weight = mesh.boneWeights[b];
        Matrix4x4 bm0 = boneMatrices[weight.boneIndex0];
        Matrix4x4 bm1 = boneMatrices[weight.boneIndex1];
        Matrix4x4 bm2 = boneMatrices[weight.boneIndex2];
        Matrix4x4 bm3 = boneMatrices[weight.boneIndex3];
        Matrix4x4 vertexMatrix = new Matrix4x4();
        for (int n = 0; n < 16; n++)
        {
            vertexMatrix[n] =
                bm0[n] * weight.weight0 +
                bm1[n] * weight.weight1 +
                bm2[n] * weight.weight2 +
                bm3[n] * weight.weight3;
        }
        var vertex = vertexMatrix.MultiplyPoint3x4(vertices[b]);
        return vertex;
    }
    public void SetPos(int b, Vector3 p)
    {
        BoneWeight weight = mesh.boneWeights[b];
        Matrix4x4 bm0 = boneMatrices[weight.boneIndex0];
        Matrix4x4 bm1 = boneMatrices[weight.boneIndex1];
        Matrix4x4 bm2 = boneMatrices[weight.boneIndex2];
        Matrix4x4 bm3 = boneMatrices[weight.boneIndex3];
        Matrix4x4 vertexMatrix = new Matrix4x4();
        for (int n = 0; n < 16; n++)
        {
            vertexMatrix[n] =
                bm0[n] * weight.weight0 +
                bm1[n] * weight.weight1 +
                bm2[n] * weight.weight2 +
                bm3[n] * weight.weight3;
        }
        vertices[b] = vertexMatrix.inverse.MultiplyPoint3x4(p);
        smr.sharedMesh.vertices = vertices;
    }
    void GetPosition()
    {
        switch (wrapper.meshType)
        {
            case MeshType.Skinned:
                SkinnedGetPos();
                break;
            case MeshType.Normal:
                break;
            default:
                break;
        }

    }
    void NormalGetPos()
    {
        //wrapper.
    }
    void SkinnedGetPos()
    {
        UpdateMatrix();
        vertices = mesh.vertices;
        for (int b = 0; b < mesh.vertexCount; b++)
        {
            points[b].transform.position = GetPos(b);
        }
    }
    public Vector3 worldPos;
    void LateUpdate()
    {
        GetPosition();
        CheckRay();
        SetPosition();
    }
}
